#pragma once

#include <nanobdd/common.h>
#include <nanobdd/node.h>

#include <map>
#include <optional>
#include <tuple>
#include <vector>

namespace nanobdd {

class Cache {
 public:
  Cache() = default;
  Cache(size_t size) : size_(size) {
    cache_.resize(size);
  }

  struct CacheEntry {
    uint32_t hash;
    std::shared_ptr<Node> node;
    uint32_t level;
    std::shared_ptr<Node> left;
    std::shared_ptr<Node> right;
    uint32_t op;
  };

  void
  insert(
      std::shared_ptr<Node> node,
      uint32_t op,
      std::shared_ptr<Node> left,
      std::shared_ptr<Node> right) {
    auto hash = TRIPLEp(op, left.get(), right.get()) % size_;
    auto& entry = cache_.at(hash);
    entry.hash = hash;
    entry.node = node;
    entry.level = node->level();
    entry.op = op;
    entry.left = left;
    entry.right = right;
  }

  std::optional<std::shared_ptr<Node>>
  lookup(uint32_t op, std::shared_ptr<Node> left, std::shared_ptr<Node> right) {
    auto hash = TRIPLEp(op, left.get(), right.get()) % size_;

    auto& entry = cache_.at(hash);
    if (entry.op == op && entry.left == left && entry.right == right) {
      return entry.node;
    }

    return std::nullopt;
  }

  void
  resize(size_t size) {
    size_ = size;
    cache_.resize(size);
  }

  size_t
  size() {
    return cache_.size();
  }

  auto&
  cache() {
    return cache_;
  }

 private:
  uint32_t size_;
  // std::map<std::tuple<uint32_t, uint32_t>, int> cache_;
  std::vector<CacheEntry> cache_;
};

} // namespace nanobdd